package io.wins.vertx.web;

import java.util.HashSet;
import java.util.Set;

import io.vertx.core.AbstractVerticle;
import io.vertx.core.Future;
import io.vertx.core.Handler;
import io.vertx.core.Vertx;
import io.vertx.core.http.HttpMethod;
import io.vertx.core.http.HttpServer;
import io.vertx.core.logging.Logger;
import io.vertx.core.logging.LoggerFactory;
import io.vertx.ext.auth.AuthProvider;
import io.vertx.ext.auth.jwt.JWTAuth;
import io.vertx.ext.web.Router;
import io.vertx.ext.web.RoutingContext;
import io.vertx.ext.web.handler.AuthHandler;
import io.vertx.ext.web.handler.BodyHandler;
import io.vertx.ext.web.handler.ChainAuthHandler;
import io.vertx.ext.web.handler.CookieHandler;
import io.vertx.ext.web.handler.CorsHandler;
import io.vertx.ext.web.handler.JWTAuthHandler;
import io.vertx.ext.web.handler.LoggerHandler;
import io.vertx.ext.web.handler.RedirectAuthHandler;
import io.vertx.ext.web.handler.SessionHandler;
import io.vertx.ext.web.handler.StaticHandler;
import io.vertx.ext.web.handler.TimeoutHandler;
import io.vertx.ext.web.sstore.LocalSessionStore;

public class Server extends AbstractVerticle {

    private final static Logger logger = LoggerFactory.getLogger(Server.class);
    private ServerProperties serverProperties;

    @Override
    public void start(final Future<Void> startFuture) throws Exception {
        this.serverProperties = ServerProperties.fromJson(this.config().getJsonObject("server"));
        final Future<HttpServer> future = Future.future();
        this.vertx.createHttpServer()
            .requestHandler(this.router(this.vertx)::accept)
            .listen(this.serverProperties.getPort(), future.completer());
        future.setHandler(ar -> {
            if (ar.succeeded()) {
                Server.logger.info("Server request timeout: [{}]ms", this.serverProperties.getRequestTimeout());
                Server.logger.info("Server request validation enabled: [{}]", this.serverProperties.isValidateRequest());
                Server.logger.info("Server started on port: [{}]", future.result().actualPort());
                startFuture.complete();
            } else {
                Server.logger.error("error", ar.cause());
                startFuture.fail(ar.cause());
            }
        });
    }

    private Router router(final Vertx vertx) {
        final Router router = Router.router(vertx);
        this.enableCorsSupport(router);
        this.enableLocalSession(router);
        router.route().handler(BodyHandler.create()
            .setUploadsDirectory(this.serverProperties.getAbsUploadPath()));
        router.route().handler(LoggerHandler.create());
        router.route().handler(TimeoutHandler.create(this.serverProperties.getRequestTimeout()));

//        router.route("/api/admin/*").handler(JWTAuthHandler.create(
//                JWTAuth.create(vertx, serverProperties.getJwtAuthConfig())));

        TokenResource.create(vertx, this.serverProperties).init(router);
        ServerResource.create(vertx, this.serverProperties).init(router);
        AttachmentResource.create(vertx, this.serverProperties).init(router);
        this.statusResource(router);
        router.route().last().handler(this::handleNotFound);
        router.route().failureHandler(this.serverProperties.isEnabledDev() ?
            ErrorHandler.create().withStackTrace() : ErrorHandler.create());
        return router;
    }

    private void enableCorsSupport(final Router router) {
        // cross origin resource sharing
        final Set<String> allowHeaders = new HashSet<>();
        allowHeaders.add("x-requested-with");
        allowHeaders.add("Access-Control-Allow-Origin");
        allowHeaders.add("origin");
        allowHeaders.add("Content-Type");
        allowHeaders.add("accept");
        final Set<HttpMethod> allowMethods = new HashSet<>();
        allowMethods.add(HttpMethod.GET);
        allowMethods.add(HttpMethod.PUT);
        allowMethods.add(HttpMethod.OPTIONS);
        allowMethods.add(HttpMethod.POST);
        allowMethods.add(HttpMethod.DELETE);
        allowMethods.add(HttpMethod.PATCH);
        router.route().handler(CorsHandler.create("*")
            .allowedHeaders(allowHeaders)
            .allowedMethods(allowMethods));
    }

    private void enableLocalSession(final Router router) {
        router.route().handler(CookieHandler.create());
        router.route().handler(SessionHandler.create(
            LocalSessionStore.create(this.vertx, "user.session")));
    }

    private void statusResource(final Router router) {
        router.route("/*").handler(StaticHandler.create("webapp"));
    }

    private Handler chainHandler(final Vertx vertx) {
        final AuthProvider authProvider = JWTAuth.create(vertx, this.serverProperties.getJwtAuthConfig());
        final AuthHandler jwt = JWTAuthHandler.create((JWTAuth) authProvider);
        final ChainAuthHandler chain = ChainAuthHandler.create();
        chain.append(jwt);
        final AuthHandler redirect = RedirectAuthHandler.create(authProvider, "/login");
        chain.append(redirect);
        return chain;
    }

    private void handleNotFound(final RoutingContext routingContext) {
        // "405 Method Not Allowed" is not support at the time being because vert.x do not handle it well
        routingContext.fail(404);
    }
}
